package mbp.agents;

import java.util.List;

import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.WakerBehaviour;
import mbp.agents.AMbpTaskAgent.AgentState;
import mbp.base.ContractState;
import mbp.base.DecisionResponse;
import mbp.base.DecisionResponse.Decision;
import mbp.base.MessageType;
import mbp.core.Contract;
import mbp.core.CounterOffer;
import mbp.core.Intention;
import mbp.core.Offer;
import mbp.core.Resource;
import mbp.logger.LoggerProxy;
import mbp.message.Message;
import mbp.message.ObjectBuilder;
import mbp.message.extra;
import mbp.message.resource;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * Implementuje schemat dzialania Agenta Podstawowego. W TRAKCIE REALIZACJI !
 * @author Wojciech Wilanski
 *
 */
public abstract class AMbpBaseAgent extends AMbpAgentMap
{
	protected String serverName;
	protected String clientName;
	
	protected Intention intention;
	protected Offer offer = null;
	protected CounterOffer counterOffer = null;
	
	public enum AgentState {
		IDLE, INTENTION, NEGOTIATION, RENEGOTIATION, ACCEPT, REALIZE
	}
	
	public enum Decision {
		TRANSFER, NEGOTIATE, REJECT
	}
	
	
	
	protected AgentState agentState;
	
	private void setAgentState(AgentState newState){
		agentState = newState;
		LoggerProxy.logStateChange(agentState.toString(), this);
	}
	
	@Override
	protected void setup()
	{
		super.setup();
		this.logger.setLevel(Level.ERROR);
		
		// Pobranie parametrow z jakimi startuje agent:
		// 1 Nazwa agenta powolujacego (String)
		// 2 Nazwa serwera uslug (String)
		// 3 Intencja (Intention)
		Object[] args = getArguments();
		if(args.length == 3)
		{
			clientName = (String) args[0];
			serverName = (String) args[1];
			intention = (Intention) args[2];			
			logger.info("Agent " + getAID().getName() + " is ready. Started with: "
					+ serverName + ", " + clientName + ", " + intention.getServiceType().getServiceTypeName());
		}
		else
		{
			logger.error("Agent " + getAID().getName() + " error parameters. Closing...");
			this.doDelete();
		}
		
		addBehaviour(new WakerBehaviour(this, 300) {
			protected void handleElapsedTimeout(){
				setAgentState(AgentState.INTENTION);
				processNegotiation('C', null, null);
			}
		});
	}
	
	@Override
	public void processMessage(Message iMsg, String senderName) 
	{
		MessageType messageType = iMsg.getMessageType();
		switch(messageType)
		{
		//Odebranie intencji
		case INTENTION:
			onIntention(iMsg, senderName);			
			break;
		case OFFER:
			onOffer(iMsg, senderName);
			break;
		case NOOFFER:
			onNoOffer(iMsg, senderName);
			break;
		case ACCEPTOFFER:
			onAcceptOffer(iMsg, senderName);
			break;
		case ACCEPTCONTRACT:
			onAcceptContract(iMsg, senderName);
			break;
		case CONTRACT:
			onContract(iMsg, senderName);
			break;
		case FREEZE:
			onFeeze(iMsg, senderName);
			break;
		case RESUME:
			onResume(iMsg, senderName);
			break;
		case REJECT:
			onReject(iMsg, senderName);
			break;
		case DONE:
			onDone(iMsg, senderName);
			break;
		case DODELETE:
			onDoDelete(iMsg, senderName);
			break;
		default:
			logger.warn("Message type: " + messageType + " not supported");
			break; 
		}		
	}
	
	private void onIntention(mbp.message.Message iMsg, String senderName)
	{
		if(agentState == AgentState.INTENTION)
		{
			processNegotiation('C', null, null);
		}
		else
		{
			logger.error("Can not process intention. Agent state is: " + agentState + ". The state should be INTENTION");
		}
	}
	
	
	/**
	 * Odebranie oferty. Komunikat od strony serwera
	 * @param iMsg
	 * @param senderName
	 */
	private  void onOffer(mbp.message.Message iMsg, String senderName)
	{
		this.offer = iMsg.getOffer();
		processNegotiation('S', this.offer.GetUUID(), MessageType.OFFER);
	}
	
	/**
	 * Odebranie checi podpisania kontraktu. Komunikat od strony Agenta Zadania
	 * @param iMsg
	 * @param senderName
	 */
	private void onAcceptOffer(mbp.message.Message iMsg, String senderName)
	{
		if(agentState == AgentState.NEGOTIATION)
		{
			Message iMsgAnswer = new Message(MessageType.ACCEPTOFFER, this.getLocalName(), serverName);
			extra ex = iMsg.getContentXml().extras.get(0);
			//List<resource> rcInAdvance = iMsg.getContentXml().resources;
			iMsgAnswer.getContentXml().extras.add(ex);;
			//iMsgAnswer.getContentXml().resources = rcInAdvance;
			SenderLogger.send(this, iMsgAnswer.GetACLMessage());
			setAgentState(AgentState.ACCEPT);
		}
		else
		{
			//// Jeszcze nie zaimplementowane
		}
	}
	
	/**
	 * Odebranie podpisanego kontraktu (Akceptacja serwera). Komunikat od strony serwera.
	 * @param iMsg
	 * @param senderName
	 */
	private void onAcceptContract(mbp.message.Message iMsg, String senderName)
	{
		if(agentState == AgentState.ACCEPT)
		{
			Message iMsgAnswer = new Message(MessageType.ACCEPTCONTRACT, this.getLocalName(), clientName );
			iMsgAnswer.getContentXml().contracts = iMsg.getContentXml().contracts;
			SenderLogger.send(this, iMsgAnswer.GetACLMessage());
			setAgentState(AgentState.REALIZE);
		}
	}
	
	
	private void onReject(mbp.message.Message iMsg, String senderName)
	{
		if(senderName.compareTo(serverName)==0)
		{
//			if( iMsg.getContentXml().extras.size() > 0 )
//			{
//				processNegotiation('S', new String(iMsg.getContentXml().extras.get(0).value), MessageType.REJECT);
//			}
//			else
//			{
//				processNegotiation('S', null, MessageType.REJECT);
//			}
			
			if(agentState == AgentState.REALIZE)
			{
				String uuid = iMsg.getContentXml().extras.get(0).value;
				sendRejectToTaskAgent(uuid, iMsg.getContentXml().resources);
				setAgentState(AgentState.IDLE);
			}
		}
		
		if(senderName.compareTo(clientName)==0)
		{
			if(agentState == AgentState.REALIZE)
			{
				Message iMsgAnswer = new Message(MessageType.REJECT, this.getLocalName(),serverName);
				iMsgAnswer.getContentXml().extras = iMsg.getContentXml().extras;
				SenderLogger.send(this, iMsgAnswer.GetACLMessage());
			}
		}
		//processNegotiation('S', null, MessageType.REJECT);
	}
	
	/**
	 * Odebranie informacji o kontrakcie od serwera i przekazanie do agenta zadania
	 * @param iMsg
	 * @param senderName
	 */
	private void onContract(mbp.message.Message iMsg, String senderName)
	{
		if(agentState == AgentState.REALIZE)
		{
			Message iMsgAnswer = new Message(MessageType.CONTRACT, this.getLocalName(), clientName );
			iMsgAnswer.getContentXml().contracts = iMsg.getContentXml().contracts;
			SenderLogger.send(this, iMsgAnswer.GetACLMessage());
		}
	}
	
	private void onDone(mbp.message.Message iMsg, String senderName)
	{
		if(agentState == AgentState.REALIZE)
		{
			Message iMsgAnswer = new Message(MessageType.DONE, this.getLocalName(), clientName );
			iMsgAnswer.getContentXml().contracts = iMsg.getContentXml().contracts;
			iMsgAnswer.getContentXml().resources = iMsg.getContentXml().resources;
			SenderLogger.send(this, iMsgAnswer.GetACLMessage());
			//doDelete();
		}
	}
	
	private void onNoOffer(mbp.message.Message iMsg, String senderName)
	{
		processNegotiation('S', null, MessageType.NOOFFER);
	}
	
	private void onFeeze(mbp.message.Message iMsg, String senderName)
	{
		if(agentState == AgentState.REALIZE)
		{
			Message iMsgAnswer = new Message(MessageType.FREEZE, this.getLocalName(), serverName );
			iMsgAnswer.getContentXml().extras = iMsg.getContentXml().extras;
			SenderLogger.send(this, iMsgAnswer.GetACLMessage());
		}
	}
	
	private void onResume(mbp.message.Message iMsg, String senderName)
	{
		if(agentState == AgentState.REALIZE)
		{
			Message iMsgAnswer = new Message(MessageType.RESUME, this.getLocalName(), serverName );
			iMsgAnswer.getContentXml().extras = iMsg.getContentXml().extras;
			SenderLogger.send(this, iMsgAnswer.GetACLMessage());
		}
	}
	
	/**
	 * Obsluga zadania: Zakonczenie pracy agenta
	 * @param iMsg
	 * @param senderName
	 */
	private void onDoDelete(mbp.message.Message iMsg, String senderName)
	{
		if(senderName.compareTo(clientName)==0)
		{
			logger.info("Deleting agent ...");
			doDelete();
		}
		else
		{
			logger.warn("Ignoring DODELETE message. Only " + clientName + " is permited to delete me.");
		}
	}
	
	
	
	private void processNegotiation(Character sender, String uuid, MessageType messageType)
	{
		switch(sender)
		{
		// Start z inicjatywy klienta ------------------------------------------.
		case 'C':
			// Intencja - brak uuid.
			if(uuid == null && messageType == null)
			{
				Intention intention = prepareIntention();
				sendIntention(intention);
			}
			// Kontroferta - dotyczy obiektu o podanym uuid.
			else
			{
				
				
			}
			break;
		case 'S':
			switch(messageType)
			{
			case OFFER:
				if(agentState == AgentState.INTENTION)
					setAgentState(AgentState.NEGOTIATION);
				
				if(agentState == AgentState.REALIZE)
					setAgentState(AgentState.RENEGOTIATION);
				
				Decision decision = decision();
				switch(decision)
				{
				case TRANSFER:
					sendOffer(this.offer);
					break;
				case NEGOTIATE:
					this.counterOffer = prepareCounterOffer();
					sendCounterOffer(this.counterOffer);
					break;
				case REJECT:
					
					break;
				}
				
				break;
			case ACCEPTCONTRACT:
				if(agentState == AgentState.ACCEPT)
				{
					
				}
				break;
			case REJECT:
				if(agentState == AgentState.REALIZE)
				{
					//sendResignation(uuid);
					//doDelete();
				}
				break;
			case NOOFFER:
				if(agentState == AgentState.INTENTION)
				{
					sendNoOffer();
				}
				break;
			}
			
			break;
		}		
	}
	
	/**
	 * Wysyla intencje do serwera uslug
	 * @param intention
	 */
	private void sendIntention(Intention intention)
	{
		Message iMsg = new Message(MessageType.INTENTION, this.getLocalName(), serverName);
		iMsg.setIntention(intention);
		SenderLogger.send(this, iMsg.GetACLMessage());
		logger.info("Message type: " + MessageType.INTENTION + " has been sent to: " + serverName);
	}
	

	/**
	 * Wysyla oferte do agenta zadania
	 * @param offer
	 */
	public  void sendOffer(Offer offer)
	{
		Message iMsg = new Message(MessageType.OFFER, this.getLocalName(), clientName);
		iMsg.setOffer(offer);
		SenderLogger.send(this, iMsg.GetACLMessage());
		logger.info("Message type: " + MessageType.OFFER + " has been sent to: " + clientName);
	}
	
	/**
	 * Wysyla kontroferte do serwera uslug
	 * @param counterOffer
	 */
	public void sendCounterOffer(CounterOffer counterOffer)
	{
		Message iMsg = new Message(MessageType.COUNTEROFFER, this.getLocalName(), serverName);
		iMsg.setCounterOffer(counterOffer);
		SenderLogger.send(this, iMsg.GetACLMessage());
		logger.info("Message type: " + MessageType.COUNTEROFFER + " has been sent to: " + serverName);
	}
	
	/**
	 * Wyslanie rezygnacji do Agenta zadania
	 * @param agentName
	 * @param uuid
	 */
	private void sendRejectToTaskAgent(String uuid, List<resource> rciToGiveBack)
	{
		if(uuid == null)uuid = "0";
		Message iMsg = new Message(MessageType.REJECT, this.getLocalName(), clientName);
		extra ex = new extra();
		ex.name = "uuid";
		ex.value = uuid;
		
		if(rciToGiveBack != null)
		{
			iMsg.getContentXml().resources = rciToGiveBack;
		}
		
		iMsg.getContentXml().extras.add(ex);
		SenderLogger.send(this, iMsg.GetACLMessage());
	}
	
	private void sendNoOffer()
	{
		Message iMsg = new Message(MessageType.NOOFFER, this.getLocalName(), clientName);
		SenderLogger.send(this, iMsg.GetACLMessage());
	}
	
	/**
	 * Przygotowanie intencji. Jesli agent bedzie sam negocjowal
	 * @return
	 */
	public abstract Intention prepareIntention();
	
	public abstract Decision decision();
	
	public abstract CounterOffer prepareCounterOffer();

}
